#include "vboindexer.h"

VBOindexer::VBOindexer()
{
    CALCULATENORMALS= false;
}

// Esto le indica a la clase si debe calcular las normales (por vertice)
void VBOindexer::CalculateNormals()
{
    CALCULATENORMALS= true;
}

// Returns true iif v1 can be considered equal to v2
bool is_near(float v1, float v2)
{
    return fabs( v1-v2 ) < 0.0001f;
}

// Searches through all already-exported vertices
// for a similar one.
// Similar = same position + same UVs + same normal
bool getSimilarVertexIndex(
    vec3 & in_vertex,
    QVector2D & in_uv,
    vec3 & in_normal,
    vector<vec3> & out_vertices,
    vector<QVector2D> & out_uvs,
    vector<vec3> & out_normals,
    unsigned short & result)
{
    // Lame linear search
    for ( unsigned int i=0; i<out_vertices.size(); i++ )
    {
        if (
                is_near( in_vertex.x , out_vertices[i].x ) &&
                is_near( in_vertex.y , out_vertices[i].y ) &&
                is_near( in_vertex.z , out_vertices[i].z ) &&
                is_near( in_uv.x()   , out_uvs     [i].x() ) &&
                is_near( in_uv.y()   , out_uvs     [i].y() ) /*&&
                is_near( in_normal.x , out_normals [i].x ) &&
                is_near( in_normal.y , out_normals [i].y ) &&
                is_near( in_normal.z , out_normals [i].z )*/
        )
        {
            result = i;          
            return true;
        }
    }
    // No other vertex could be used instead.
    // Looks like we'll have to add it to the VBO.
    return false;
}

struct PackedVertex
{
    vec3 position;
    vec3 normal;
    bool operator<(const PackedVertex that) const
    {
        return memcmp((void*)this, (void*)&that, sizeof(PackedVertex))>0;
    };
};

bool getSimilarVertexIndex_fast(
    PackedVertex & packed,
    map <PackedVertex, unsigned short> & VertexToOutIndex,
    unsigned short & result
)
{
    map<PackedVertex,unsigned short>::iterator it = VertexToOutIndex.find(packed);

    if ( it == VertexToOutIndex.end() )
    {
        return false;
    }
    else
    {
        unsigned short & i= it->second;

        result = it->second;
        return true;
    }
}

bool getSimilarVertexIndex_fastNoNormals(PackedVertex & input, map <vec3,unsigned short> & VertexToOutIndex, unsigned short & result, vector<vec3> & out_normals)
{
    vec3 searchVertex= input.position;
    map<vec3,unsigned short>::iterator it = VertexToOutIndex.find(searchVertex);

    if ( it == VertexToOutIndex.end() )
    {
        return false;
    }
    else
    {
        unsigned short & i= it->second;

        out_normals[i].x+= input.normal.x;
        out_normals[i].y+= input.normal.y;
        out_normals[i].z+= input.normal.z;

        //qDebug("Similar!");

        result = it->second;
        return true;
    }
}

void VBOindexer::indexVBO (vector<vec3> &in_vertices,
               vector<vec3> &in_normals,
               vector<unsigned short> &out_indices,
               vector<vec3> &out_vertices,
               vector<vec3> &out_normals)
{
    map<PackedVertex,unsigned short> VertexToOutIndex;
    map<vec3, unsigned short> NormalsToOut;

    // For each input vertex
    for ( unsigned int i=0; i<in_vertices.size(); i++ )
    {
        PackedVertex packed = {in_vertices[i], in_normals[i]};

        // Try to find a similar vertex in out_XXXX
        unsigned short index;

        bool found;

        if (CALCULATENORMALS)
            found= getSimilarVertexIndex_fastNoNormals(packed, NormalsToOut, index, out_normals);
        else
            found= getSimilarVertexIndex_fast( packed, VertexToOutIndex, index);

        if ( found ) // A similar vertex is already in the VBO, use it instead !
        {
            out_indices.push_back( index );
        }
        else
        { // If not, it needs to be added in the output data.
            out_vertices.push_back( in_vertices[i]);
            out_normals .push_back( in_normals[i]);
            unsigned short newindex = (unsigned short)out_vertices.size() - 1;
            out_indices .push_back( newindex );

            if (CALCULATENORMALS)
                NormalsToOut[ in_vertices[i] ]= newindex;
            else
                VertexToOutIndex[ packed ] = newindex;
        }
    }

    // Ahora podemos recalcular las normales
    for ( unsigned int i=0; i<out_normals.size(); i++)
    {
        float fact= fabs(out_normals[i].x + out_normals[i].y + out_normals[i].z);

        out_normals[i].x/= fact;
        out_normals[i].y/= fact;
        out_normals[i].z/= fact;
    }
}


/** PARA EL VBO indexado con coordenadas UV **/

struct PackedVertexUV
{
    vec3 position;
    vec2 uv;
    vec3 normal;
    bool operator<(const PackedVertexUV that) const
    {
        return memcmp((void*)this, (void*)&that, sizeof(PackedVertexUV))>0;
    };
};

bool getSimilarVertexIndex_fastUV(
    PackedVertexUV & packed,
    map<PackedVertexUV,unsigned short> & VertexToOutIndex,
    unsigned short & result
)
{
    map<PackedVertexUV,unsigned short>::iterator it = VertexToOutIndex.find(packed);
    if ( it == VertexToOutIndex.end() )
    {
        return false;
    }
    else
    {
        result = it->second;
        return true;
    }
}

void VBOindexer::indexVBOwuv
(
    vector<vec3> & in_vertices,
    vector<vec2> & in_uvs,
    vector<vec3> & in_normals,

    vector<unsigned short> & out_indices,
    vector<vec3> & out_vertices,
    vector<vec2> & out_uvs,
    vector<vec3> & out_normals
)
{
    map<PackedVertexUV,unsigned short> VertexToOutIndex;

    // For each input vertex
    for ( unsigned int i=0; i<in_vertices.size(); i++ )
    {
        PackedVertexUV packed = {in_vertices[i], in_uvs[i], in_normals[i]};


        // Try to find a similar vertex in out_XXXX
        unsigned short index;
        bool found = getSimilarVertexIndex_fastUV( packed, VertexToOutIndex, index);

        if ( found )
        { // A similar vertex is already in the VBO, use it instead !
            out_indices.push_back( index );
        }
        else
        { // If not, it needs to be added in the output data.
            out_vertices.push_back( in_vertices[i]);
            out_uvs     .push_back( in_uvs[i]);
            out_normals .push_back( in_normals[i]);
            unsigned short newindex = (unsigned short)out_vertices.size() - 1;
            out_indices .push_back( newindex );
            VertexToOutIndex[ packed ] = newindex;
        }
    }
}
